<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<script src="../dist/iife/spine-webgl.js"></script>
<style>
	html,
	body {
		margin: 0;
		padding: 0;
	}

	#container {
		display: flex;
		width: 100vw;
		height: 100vh;
	}

	#skins {
		width: 100px;
		flex-shrink: 0;
		overflow: scroll;
	}

	#canvas {
		width: calc(100% - 100px);
	}
</style>

<body>
	<div id="container">
		<div id="skins"></div>
		<canvas id="canvas"></canvas>
	</div>

	<script>
		// Define the class running in the Spine canvas
		class App {

			constructor() {
				this.canvas = null;
				this.atlas = null;
				this.skeletonData = null;
				this.skeleton = null;
				this.state = null;
				this.selectedSkins = [];
				this.skinThumbnails = {};
				this.lastBounds = {};
			}

			loadAssets(canvas) {
				canvas.assetManager.AnimationState
				canvas.assetManager.loadTextureAtlas("mix-and-match-pma.atlas");
				canvas.assetManager.loadBinary("mix-and-match-pro.skel");
			}

			initialize(canvas) {
				this.canvas = canvas;
				let assetManager = canvas.assetManager;

				// Create the atlas
				this.atlas = canvas.assetManager.require("mix-and-match-pma.atlas");
				let atlasLoader = new spine.AtlasAttachmentLoader(this.atlas);

				// Create the skeleton
				let skeletonBinary = new spine.SkeletonBinary(atlasLoader);
				this.skeletonData = skeletonBinary.readSkeletonData(assetManager.require("mix-and-match-pro.skel"));
				this.skeleton = new spine.Skeleton(this.skeletonData);

				// Create the animation state
				let stateData = new spine.AnimationStateData(this.skeletonData);
				this.state = new spine.AnimationState(stateData);
				this.state.setAnimation(0, "dance", true);

				// Create the user interface to selecting skins
				this.createUI(canvas);

				// Create a default skin.
				this.addSkin("skin-base");
				this.addSkin("nose/short");
				this.addSkin("eyelids/girly");
				this.addSkin("eyes/violet");
				this.addSkin("hair/brown");
				this.addSkin("clothes/hoodie-orange");
				this.addSkin("legs/pants-jeans");
				this.addSkin("accessories/bag");
				this.addSkin("accessories/hat-red-yellow");
			}

			addSkin(skinName) {
				if (this.selectedSkins.indexOf(skinName) != -1) return;
				this.selectedSkins.push(skinName);
				let thumbnail = this.skinThumbnails[skinName];
				thumbnail.isSet = true;
				thumbnail.style.filter = "none";
				this.updateSkin();
			}

			removeSkin(skinName) {
				let index = this.selectedSkins.indexOf(skinName);
				if (index == -1) return;
				this.selectedSkins.splice(index, 1);
				let thumbnail = this.skinThumbnails[skinName];
				thumbnail.isSet = false;
				thumbnail.style.filter = "grayscale(1)";
				this.updateSkin();
			}

			updateSkin() {
				// Create a new skin from all the selected skins.
				let newSkin = new spine.Skin("custom-skin");
				for (var skinName of this.selectedSkins) {
					newSkin.addSkin(this.skeletonData.findSkin(skinName));
				}
				this.skeleton.setSkin(newSkin);
				this.skeleton.setToSetupPose();
				this.skeleton.updateWorldTransform(spine.Physics.update);

				// Calculate the bounds so we can center and zoom
				// the camera such that the skeleton is in full view.
				let offset = new spine.Vector2(), size = new spine.Vector2();
				this.skeleton.getBounds(offset, size);
				this.lastBounds = { offset: offset, size: size };
			}

			createUI(canvas) {
				const THUMBNAIL_SIZE = 100;
				let renderer = canvas.renderer;
				let camera = renderer.camera;

				// We'll reuse the webgl context used to render the skeleton for
				// thumbnail generation. We temporarily resize it to 300x300 pixels
				// Note: we passed `preserveDrawingBuffer: true` to the SpineCanvas
				// constructor. Without it, we could not fetch the pixel data from
				// the canvas after rendering.
				let oldWidth = canvas.htmlCanvas.width;
				let oldHeight = canvas.htmlCanvas.height;
				canvas.htmlCanvas.width = canvas.htmlCanvas.height = THUMBNAIL_SIZE;
				canvas.gl.viewport(0, 0, THUMBNAIL_SIZE, THUMBNAIL_SIZE);

				// For each skin, generate at thumbnail as follows
				// 1. Set it on the skeleton
				// 2. Determine its bounds
				// 3. Center and scale it to the offscreen canvas and render it
				// 4. Fetch the rendered image from the canvas and store it.
				let images = [];
				for (var skin of this.skeletonData.skins) {
					// Skip the empty default skin
					if (skin.name === "default") continue;

					// Set the skin, then update the skeleton
					// to the setup pose and calculate the world transforms
					this.skeleton.setSkin(skin);
					this.skeleton.setToSetupPose();
					this.skeleton.updateWorldTransform(spine.Physics.update);

					// Calculate the bounding box enclosing the skeleton.
					let offset = new spine.Vector2(), size = new spine.Vector2();
					this.skeleton.getBounds(offset, size);

					// Position the renderer camera on the center of the bounds, and
					// set the zoom so the full skin is visible within the 300x300
					// rendering area. We leave 10% of empty space around a skin in the
					// thumbnail, hence the multiplication of 1.1 for the zoom factor.
					camera.position.x = offset.x + size.x / 2;
					camera.position.y = offset.y + size.y / 2;
					camera.zoom = size.x > size.y ? size.x / THUMBNAIL_SIZE * 1.1 : size.y / THUMBNAIL_SIZE * 1.1;
					camera.setViewport(THUMBNAIL_SIZE, THUMBNAIL_SIZE);
					camera.update();

					// Clear the canvas and render the skeleton
					canvas.clear(0.5, 0.5, 0.5, 1);
					renderer.begin();
					renderer.drawSkeleton(this.skeleton, true);
					renderer.end();

					// Get the image data and convert it to an img element
					let image = new Image();
					image.src = canvas.htmlCanvas.toDataURL();
					image.skinName = skin.name;
					image.isSet = false;
					image.style.filter = "grayscale(1)";

					// Set up a click listener that will add/remove the skin
					image.onclick = () => {
						if (image.isSet) this.removeSkin(image.skinName);
						else this.addSkin(image.skinName);
					}

					// Store the thumbail image in the list of all skin
					// thumbnails.
					images.push(image);
					this.skinThumbnails[image.skinName] = image;
				}

				// Sort the list of skin thumbnails by name, so items
				// from the same folder end up next to each other.
				images.sort((a, b) => {
					return a.skinName > b.skinName ? 1 : -1;
				});

				// Add the thumbnails to the skins <div>
				let skinsDiv = document.getElementById("skins");
				for (var thumbnail of images) {
					skinsDiv.appendChild(thumbnail);
				}

				// Restore the canvas size and camera of the renderer
				canvas.htmlCanvas.width = oldWidth;
				canvas.htmlCanvas.height = oldHeight;
				renderer.resize(spine.ResizeMode.Expand);
				camera.position.x = 0;
				camera.position.y = 0;
				camera.zoom = 1;
			}

			update(canvas, delta) {
				this.state.update(delta);
				this.state.apply(this.skeleton);
				this.skeleton.updateWorldTransform(spine.Physics.update);
			}

			render(canvas) {
				// Center and zoom the camera so the skeleton is
				// in full view irrespective of the page size.
				let renderer = canvas.renderer;
				let camera = renderer.camera;
				renderer.resize(spine.ResizeMode.Expand);
				let offset = this.lastBounds.offset, size = this.lastBounds.size;
				camera.position.x = offset.x + size.x / 2;
				camera.position.y = offset.y + size.y / 2;
				camera.zoom = size.x > size.y ? size.x / this.canvas.htmlCanvas.width * 1.1 : size.y / this.canvas.htmlCanvas.height * 1.1;
				camera.update();

				canvas.clear(0.2, 0.2, 0.2, 1);
				renderer.begin();
				renderer.drawSkeleton(this.skeleton, true);
				renderer.end();
			}
		}

		// Create the Spine canvas which runs the app
		new spine.SpineCanvas(document.getElementById("canvas"), {
			pathPrefix: "/assets/",
			app: new App()
		});
	</script>
</body>

</html>